﻿using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Felbot.Interface;
using Felbot.Interface.Conversations;
using MBNCSUtil;

namespace Felbot.Protocol {
    /// <summary>This is the base object for all Felbot PacketThreads. It gives you proxy support and a socket control.</summary>
    public abstract class PacketThread {
        /// <summary>This is the profile form that this packet thread is used by.</summary>
        internal frmProfile m_frmProfile;
        /// <summary>The socket that this packet thread connects with.</summary>
        internal Socket m_Socket;
        /// <summary>The state monitor timer.</summary>
        internal Timer m_SocketStateMonitorTimer;
        /// <summary>The thread that this object is on.</summary>
        internal Thread m_SocketReader;

        /// <summary>This determines how many times it has attempted to reconnect after losing connection.</summary>
        internal int m_ReconnectAttempts = 0;
        /// <summary>This stores whether we are trying to reconnect after a disconnect.</summary>
        internal bool m_ReconnectInQueue = false;

        /// <summary>This is true when the socket is connected.</summary>
        internal bool m_SocketConnected = false;
        /// <summary>This is true when proxy settings are negotiated, and you can send packets specific to the thread.</summary>
        internal bool m_Connected = false;
        /// <summary>This is true when Felbot is waiting for the socket to close.</summary>
        internal bool m_Disconnecting = false;

        /// <summary>Creates a new PacketThread. Please construct inheriting classes calling this.</summary>
        /// <param name="m_frmProfile">A reference to the profile form that this packet thread is used by.</param>
        public PacketThread(frmProfile main) {
            m_frmProfile = main;
        }

        /// <summary>This abstract function should be overridden to recieve a packet and parse it.</summary>
        /// <param name="drPacket">A DataReader containing the packet data.</param>
        abstract protected void ParsePacket(DataReader packet);

        /// <summary>
        /// This is called from CheckStateChange when the connection is
        /// finished. This checks for proxy settings, and sends the.
        /// </summary>
        internal void Socket_Connected() {
            if (m_frmProfile.Profile.GetSetting("AdvConn", "ProxyUse").ToBoolean(false)) {
                switch (m_frmProfile.Profile.GetSetting("AdvConn", "ProxyProtocol").ToInteger(0)) {
                    case 0: //use SOCKS 5
                        m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Try, "[PROXY] [SOCKS5] Sending method and request.."));
                        SendSOCKS5Header();
                        return;
                    case 1: //use SOCKS 4
                        m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Try, "[PROXY] [SOCKS4] Sending method and request.."));
                        SendSOCKS4Header();
                        break;
                    default: //what is user doing?
                        m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] Unknown or unimplemented proxy protocol."));
                        Socket_Disconnect();
                        return;
                }
            }

            Socket_ConnectedComplete();
        }

        /// <summary>
        /// Sends the SOCKS4 method and request command.
        /// </summary>
        private void SendSOCKS4Header() {
            DataBuffer socksPacket = new DataBuffer();
            socksPacket.InsertByte(0x04); //socks version
            socksPacket.InsertByte(0x01); //cmd CONNECT
            socksPacket.InsertByte(0x17);
            socksPacket.InsertByte(0xE0);
            //m_dbSocksPacket.InsertInt16((short) IPAddress.HostToNetworkOrder(6112)); //port
            string server = m_frmProfile.Profile.GetSetting("Connection", "Server").ToString("");
            IPAddress address;
            if (!IPAddress.TryParse(server, out address)) {
                try {
                    address = IPAddress.Parse(new TcpClient(server, 6112).Client.RemoteEndPoint.ToString().Split(':')[0]);
                } catch (SocketException) {
                    m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS4] Proxy connection failed! Unable to resolve \"" + server + "\" to an IP address."));
                    return;
                }
                socksPacket.InsertByteArray(address.GetAddressBytes()); //destination ip
            } else {
                socksPacket.InsertByteArray(address.GetAddressBytes()); //destination ip
            }
            socksPacket.InsertCString(""); //username, null terminated
            SendProxyPacket(socksPacket.GetData());
        }

        /// <summary>
        /// Sends the SOCKS5 method command followed by the SOCKS5 request command.
        /// </summary>
        private void SendSOCKS5Header() {
            DataBuffer socksPacket;

            socksPacket = new DataBuffer();
            socksPacket.InsertByte(0x05); //socks version
            socksPacket.InsertByte(0x01); //one method
            socksPacket.InsertByte(0x00); //method one = no authentication
            SendProxyPacket(socksPacket.GetData());

            socksPacket = new DataBuffer();
            socksPacket.InsertByte(0x05); //socks version
            socksPacket.InsertByte(0x01); //cmd CONNECT
            socksPacket.InsertByte(0x00); //null
            socksPacket.InsertByte(0x01); //address type IPv4

            string server = m_frmProfile.Profile.GetSetting("Connection", "Server").ToString("");
            IPAddress address;
            if (!IPAddress.TryParse(server, out address)) {
                try {
                    address = IPAddress.Parse(new TcpClient(server, 6112).Client.RemoteEndPoint.ToString().Split(':')[0]);
                } catch (SocketException) {
                    m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Proxy connection failed! Unable to resolve \"" + server + "\" to an IP address."));
                    return;
                }
                socksPacket.InsertByteArray(address.GetAddressBytes()); //destination ip
            } else {
                socksPacket.InsertByteArray(address.GetAddressBytes()); //destination ip
            }
            socksPacket.InsertByte(0x17);
            socksPacket.InsertByte(0xE0);
            //m_dbSocksPacket.InsertInt16((short) IPAddress.HostToNetworkOrder(6112)); //port
            SendProxyPacket(socksPacket.GetData());
        }

        /// <summary>Sends a proxy packet and prints any possible raw packet display.</summary>
        /// <param name="m_bData">The data to send.</param>
        private void SendProxyPacket(byte[] data) {
            if (m_Disconnecting || !m_SocketConnected)
                return;
            try {
                m_Socket.Send(data);
                if (Program.MainForm.ShowPackets) {
                    m_frmProfile.RichTextBox.AddTextNodes(
                        new TextNode(
                            Color.DodgerBlue,
                            "[PROXY] SENT PACKET:" + Environment.NewLine +
                            DataFormatter.Format(data)
                        )
                    );
                }
            } catch (Exception e) {
                if (e.Message == "An existing connection was forcibly closed by the remote host") {
                    if (m_ReconnectAttempts > 1) {
                        m_ReconnectAttempts = 0;
                        m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] You are IPBanned, or you have lost your internet connection.  Please try connecting later."));
                        Socket_Disconnect();
                        return;
                    } else {
                        m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] You have been IPBanned or your connection has been interrupted, reconnecting.. (" + (++m_ReconnectAttempts).ToString() + ")"));
                        Socket_Connect();
                        return;
                    }
                }
                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] Caught exception in SendProxyPacket(): " + Environment.NewLine + e.ToString()));
            }
        }

        /// <summary>
        /// This must be overridden to be used to disconnect the socket.
        /// </summary>
        virtual public bool Socket_Disconnect() {
            m_Disconnecting = true;
            m_Connected = false;
            if (m_Socket != null) {
                try  {
                    if (m_Socket.Connected)
                        m_Socket.Disconnect(false);
                } catch (SocketException) { }
                if (m_SocketConnected) {
                    return false;
                }
            }
            if (m_ReconnectInQueue) {
                m_ReconnectInQueue = false;
                Socket_Connect();
            }
            m_Disconnecting = false;
            return true;
        }

        /// <summary>
        /// This must be overridden to be used to connect the socket.
        /// </summary>
        virtual public bool Socket_Connect() {
            if (m_SocketConnected) {
                m_ReconnectInQueue = true;
                Socket_Disconnect();
                return false;
            }
            return true;
        }

        abstract internal void Socket_ConnectedComplete();
        abstract internal void SendPacket(byte[] packet);
        abstract internal void SendByte(byte byteValue);

        /// <summary>Call this from extended objects when PROXY connection preliminary packets should be sent.</summary>
        protected void Proxy() {
            byte[] data = new byte[m_Socket.Available];
            if (m_Socket.Available >= 2) //possible SOCKS response
            {
                #region Proxy: SOCKS5
                if (m_Socket.Available == 2) {
                    m_Socket.Receive(data, 0, 1, SocketFlags.Peek);

                    if (data[0] == 0x05) //SOCKS5 PACKET
                    {
                        m_Socket.Receive(data, 0, m_Socket.Available, SocketFlags.None);
                        if (Program.MainForm.ShowPackets) {
                            m_frmProfile.RichTextBox.AddTextNodes(
                                new TextNode(
                                    Color.DodgerBlue,
                                    "[PROXY] RECIEVED PACKET:" + Environment.NewLine +
                                    DataFormatter.Format(data)
                                )
                            );
                        }
                        DataReader reader = new DataReader(data);
                        reader.ReadByte(); //0x05 protocol
                        byte resultCode = reader.ReadByte();
                        switch (resultCode) //reply code
                        {
                            case 0x00: //succeeded
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Success, "[PROXY] [SOCKS5] Valid method established; proxy reports connection success."));
                                Socket_ConnectedComplete();
                                break;
                            case 0x01:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! General server failure."));
                                Socket_Disconnect();
                                break;
                            case 0x02:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Proxy connection failed! Connection not allowed by ruleset."));
                                Socket_Disconnect();
                                break;
                            case 0x03:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Network unreachable."));
                                Socket_Disconnect();
                                break;
                            case 0x04:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Host unreachable."));
                                Socket_Disconnect();
                                break;
                            case 0x05:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Connection refused."));
                                Socket_Disconnect();
                                break;
                            case 0x06:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! TTL expired."));
                                Socket_Disconnect();
                                break;
                            case 0x07:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Command not supported."));
                                Socket_Disconnect();
                                break;
                            case 0x08:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Address type not support."));
                                Socket_Disconnect();
                                break;
                            default:
                                m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Bot_Fail, "[PROXY] [SOCKS5] Prozy connection failed! Unknown failure (" + resultCode.ToString("X") + ")."));
                                Socket_Disconnect();
                                break;
                        }
                    }
                }
                #endregion
            }
        }
    }
}
